Android — 事件分发机制(一)
前言
事件分发机制对于Android开发人员可以说是一个非常敏感的词汇,现在网上关于事件分发机制的讲解很多都是参考郭大叔的博客以及《Android开发艺术探索》。所以学习事件分发机制还是推荐看一下博文:
Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
强烈希望大家看以上博文,而忽略本文。
本菜鸡只是记录一下学习过程中比较重要的知识点。
初识
|
|
如果Button两个事件都注册了,那么哪一个事件会先执行呢?
事实证明,onTouch事件会优先于onClick事件执行,而且onTouch方法是有返回值的,如果返回true,onClick事件就不会执行了,也就是说这个事件被onTouch消费掉了,不会往下传递了。
我们知道,所有的控件都是继承至View的,只要你触摸了任何一个控件,就一定会调用该控件的dispatchTouchEvent()方法,源码:
|
|
第一个条件:
mOnTouchListener变量是在setOnTouchListener()方法赋值的:
|
|
也就是说只要我们注册了Touch事件,mOnTouchListener就一定被赋值了。
第二个条件:
是判断当前点击的空间是否是enable的,按钮类控件默认都是enable的,因此这个条件恒定为true。
第三个条件:
回调了mOnTouchListener.onTouch(this, event),如果这个方法返回true,则dispatchTouchEvent()返回true,就不会往下执行了。如果这个方法返回flase,就会去执行onTouchEvent(event)方法。
这也说明一个很重要的问题,那就是onClick的调用是在onTouchEvent()方法中。
onTouchEvent()源码:
|
|
switch判断中,当case为MotionEvent.ACTION_UP时,在经过种种判断之后进入performClick()方法中。performClick():
|
|
可以看到只要mOnClickListener不为空,就会去调用它的onClick方法,而mOnClickListener又是在哪赋值的呢?其实和上面变量mOnTouchListener的赋值是一样的,即:
|
|
也就是在注册点击事件的时候就会赋值,然后会在performClick()方法中回调被点击控件的onClick()方法。
然后,我们再来认识一下Touch事件的层级传递。
如果给一个控件注册了Touch事件每次点击它的时候都会触发一系列的ACTION_DWON,ACTION_MOVE,ACTION_UP等事件。这里需要注意的是,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其他的ACTION就不会在得到执行了,简单来说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个ACTION返回true,才会触发后一个ACTION。
说到这里,其实要注意一点,如果在onTouch方法中返回了false,则会进入onTouchEvent()方法中,这里onTouchEvent()也是可以返回true的导致最终dispatchTouchEvent()方法返回true。主要在于控件是否是默认可点击的。
重点:
onTouch()和onTouchEvent()的区别
从源码中可以看出,两个方法都是在View的dispatchTouchEvent()中调用的,onTouch()优先于onTouchEvent()执行。如果在onTouch()方法中通过返回true将事件消费掉,onTouchEvent()将不会在执行。
另外需要注意的是,onTouch()方法能够执行需要两个前提条件,第一是mOnTouchListener的值不能为空,第二个是控件必须上可点击的。如果控件是不可点击的,则给它注册Touch事件将不会执行,对于这一类控件,如果我们想要监听它的事件,就必须通过重写onTouchEvent()方法来实现。
再识
Android中的Touch事件的传递,绝对是先传递到ViewGroup,在传递给View的。
在上面我们说过,只有你触摸了任何控件,都会去调用该控件的dispatchTouchEvent()方法,这个说法没错,但是不够完整。实际情况是,当你点击了某个控件,首先会去调用该控件所在布局的dispatchTouchEvent()方法,然后在布局的dispatchTouchEvent()方法中找到被点击的相应控件,在去调用控件的dispatchTouchEvent()方法。
ViewGroup的dispatchTouchEvent()方法源码:
|
|
在第十三行我们可以看到一个条件判断,如果disallowIntercept和 !onInterceptTouchEvent()两者有一个true,就会进入这个条件判断。disallowIntercept是指是否禁用掉事件拦截功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent()方法对这个值进行修改。当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入条件判断的内部。第二个值是对onInterceptTouchEvent()方法的返回值取反。如果这个方法返回true,就进不去条件判断内部,按钮点击事件就都被屏蔽了,也就是按钮点击事件的处理是在条件判断内部进行的。
首先是通过一个for循环遍历当前ViewGroup下的所有的子View,然后在判断当前遍历的View是否是正在点击的View,如果是就是进入条件判断的内部,然后在调用该View的dispatchTouchEvent()。
这里需要注意的是,调用子View的dispatchTouchEvent()是有返回值的,我们已经知道,如果一个控件是可点击的,那么点击该控件时,dispatchTouchEvent()返回值必定为true,因此就会给ViewGroup的dispatchTouchEvent()方法直接返回了true,这也就导致后面的代码无法执行了。如果这个时候没有返回true,则会往下执行,如果target为null,则会进入条件判断内部,一般情况下target都是为null的,因此会调用super.dispatchTouchEvent()。
总结
- Android 事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。
- 在ViewGroup中可以通过onInterceptTouchEvent()方法对事件传递进行拦截。onInterceptTouchEvent()返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。
- 子View中如果将传递的事件消费掉。ViewGroup中将无法接受到任何事件。